package parser

import "fmt"
import "markers"
import "lexy"

func GetMarkers( stuff [][]byte ) []markers.Marker {
    // count := 0
    // result := make([]Marker,100)
    bytes := make(chan(lexy.Byte))
    tokens := make(chan(lexy.Token))
    go lexy.ReadBytes( bytes, stuff )
    go lexy.ReadTokens( tokens, bytes )
    source := &lexy.Source{<-tokens, tokens, make([]markers.Marker, 0, 10)}
    tree := parse( source )
    require( source, lexy.EOF, "expression must not be followed by tokens" )
    for _, m := range NameAnalysis( tree ) {
        source.AddMarker(*m)
    }
    if len(source.Markerz) == 0 {
        comment := "tree: " + tree.String()
        m := markers.Marker{markers.Span{markers.Point{0, 0}, len(comment)}, comment}
        source.AddMarker( m )
        }
    // return reverse( source.markers )
    return source.Markerz
}

func reverse( m []markers.Marker ) []markers.Marker{
    l := len(m)
    for i, j := 0, l; i < l/2; i += 1 {
        j -= 1 
        m[i], m[j] = m[j], m[i]
    }
    return m
}

type Tree interface {
    String() string
}

type bindings struct {
    outer *bindings
}

type Messages map[*markers.Marker]*markers.Marker

func (m *Messages) add( message string ) {
    x := &markers.Marker{markers.Span{markers.Point{1, 1}, 1}, message}
    (*m)[x] = x
}

func (m *Messages) addAt( covers markers.Span, message string ) {
    x := &markers.Marker{covers, message}
    (*m)[x] = x
}

type Env struct {
    current map[string]*bindings
    undo *Undo
}

func emptyEnv() *Env {
    return &Env{make(map[string]*bindings), &Undo{"", nil}}
}

type Undo struct {
    name string
    next *Undo
}

func NameAnalysis( t Tree ) Messages {
    result := make(Messages)
    env := emptyEnv()
    nameAnalysis( t, env, result )
    return result
}

func bind( t Tree, env *Env, messages Messages ) {
    switch T := t.(type) {
        case *comma:
            bind( T.l, env, messages )
            bind( T.r, env, messages )
            
        case *id:
            name := T.spelling
            env.current[name] = &bindings{env.current[name]}
            env.undo = &Undo{name, env.undo}
        
        default:
            messages.add( "illegal as binding: " + t.String() )
    }
}

func (e *Env) present(name string) bool {
    entry, present := e.current[name]
    return present && entry != nil
}

func block( e *Env ) {
    e.undo = &Undo{ "", e.undo }
}

func unbind( e *Env ) {
    for e.undo.name != "" {
        name := e.undo.name
        e.current[name] = e.current[name].outer
        e.undo = e.undo.next
    }
    e.undo = e.undo.next
}

func nameAnalysis( t Tree, env *Env, messages Messages ) {
   switch T := t.(type) {
	   case *lambda:
	       block( env )
	       bind( T.args, env, messages )
	       nameAnalysis( T.body, env, messages )
	       unbind( env )
	       return
	        
	   case *iflike:
           nameAnalysis( T.test, env, messages )
           nameAnalysis( T.ifSo, env, messages )
           nameAnalysis( T.ifNot, env, messages )
	        
	   case *comma:
	       nameAnalysis( T.l, env, messages )
           nameAnalysis( T.r, env, messages )
	        
	   case *id:
           if !env.present(T.spelling) {
               messages.addAt( T.covers, T.spelling + " not declared." )       
           }
	       
	   case num:
	   case stringy:
	   case *missing:
	        // no names to do anything with
	        
	   case *infix:
           nameAnalysis( T.l, env, messages )
           nameAnalysis( T.r, env, messages )
	        
	   default:
	       messages.add( "unknown thingy: " + t.String() )
    }
}



/*
type Type interface {

}

type Message interface { String() string }

var empty Type
var boolean Type

var none Message

func seq(a, b Type) Type {}

func checkSame(a, b Type) Message {}

func all(m...Message) Message {}

func leftType(t Type) Type {}

func rightType(t Type) Type {}

func resultType(t Type) Type {}

func TypeOfOperator(op string) (Type, Message) {}

func TypeCheck(t Tree) (Type, Message) {
    switch T := t.(type) {
        case *lambda:
            a, ma := TypeCheck(T.body)
            return a, ma
            
        case *iflike:
            a, ma := TypeCheck(T.test)
            b, mb := TypeCheck(T.ifSo)
            c, mc := TypeCheck(T.ifNot)
            md := checkSame(a, boolean)
            me := checkSame(b, c)
            return c, all(ma, mb, mc, md, me)
            
        case *comma:
            a, ma := TypeCheck(T.l)
            b, mb := TypeCheck(T.r)
            return seq(a, b), all(ma, mb)
            
        case label:
            return CCC, none
            
        case *missing:
            return empty, none
            
        case *infix:
            a, ma := TypeCheck(T.l)
            b, mb := TypeOfOperator(T.op)
            c, mc := TypeCheck(T.r)
            md := checkSame(a, leftType(b))
            me := checkSame(c, rightType(b))
            return resultType(b), all(ma, mb, mc, md, me)
            
        default:
            return empty, none
    }
    return empty, none
}
*/

type lambda struct{args, body Tree}

func (i *lambda) String() string {
    return "(" + i.args.String() + " => " + i.body.String() + ")"
}

type iflike struct {test, ifSo, ifNot Tree}

func (i *iflike) String() string {
    return "(" + i.test.String() + " | " + i.ifSo.String() + " | " + i.ifNot.String() + ")"
}

type comma struct{l, r Tree}

func (c *comma) String() string {
    return c.l.String() + ", " + c.r.String()
}

type id struct {covers markers.Span; spelling string}

func (i *id) String() string {
    return i.spelling
}

type num string

func (n num) String() string {
    return string(n)
}

type stringy string

func (s stringy) String() string {
    return string(s)
}

type missing struct{}

func (m *missing) String() string {
    return "<missing>"
}

type infix struct {l Tree; op string; r Tree}

func (i *infix) String() string {
    return "(" + i.l.String() + i.op + i.r.String() + ")"
}

func parse( s *lexy.Source ) Tree {
    l := parseItem( s )
    if s.Current.Class == lexy.COMMA {
        s.Advance()
        r := parse( s )
        l = &comma{l, r}
    }
    return l
}

func parseItem( s *lexy.Source ) Tree {
    l := parseOperand( s )
    for s.Current.Class == lexy.OP {
        op := s.Current.Spelling 
        s.Advance(); 
        r := parseOperand( s )
        l = &infix{l, op, r} 
    }
    return l
}


func parseOperand( s *lexy.Source ) Tree {
    if s.Current.Class == lexy.ID  {
        tree := &id{markers.Span{s.Current.Start, len(s.Current.Spelling)}, s.Current.Spelling}
        if s.Current.Spelling == "z" {
            complain( s, "detected " + s.Current.Spelling )
        }
        s.Advance()
        return tree
    } else if s.Current.Class == lexy.NUM {
        tree := num(s.Current.Spelling)
        s.Advance()
        return tree
    } else if s.Current.Class == lexy.STRING {
        tree := stringy(s.Current.Spelling)
        s.Advance()
        return tree
    } else if s.Current.Class == lexy.LPAR {
        s.Advance()
        tree := parse( s )
        switch s.Current.Class {
            case lexy.RPAR:
                s.Advance()
                return tree
                
            case lexy.ARROW:
                s.Advance()
                r := parse( s )
                require( s, lexy.RPAR, "closing parenthesis required"  )
                return &lambda{asFormalArgs(s, tree), r}
                
            case lexy.BAR:
                s.Advance()
                r := parse( s )
                var t Tree = &missing{}
                if s.Current.Class == lexy.BAR {
                    s.Advance()
                    t = parse( s )
                }
                require( s, lexy.RPAR, "closing parenthesis required"  )
                return &iflike{tree, r, t}
        }
        return tree
    } else {
        complain( s, "operand expected at " + s.Current.Spelling )
        return &missing{}
    }
    return &missing{}
}

func asFormalArgs( s *lexy.Source,  p Tree ) Tree {
    // if _, ok := p.(label); !ok {
    //     complain( s, "not legal as arguments: " + p.String() )
    // } 
    return p
}

func span(t lexy.Token) markers.Span {
    return markers.Span{t.Start, len(t.Spelling)}
}

func require( s *lexy.Source, c lexy.Class, message string ) {
    if s.Current.Class != c {
        class := fmt.Sprint( s.Current.Class )
        m := markers.Marker{span(s.Current), message + " [at " + s.Current.Spelling + ": " + class + "]"}
        s.AddMarker( m )
    } else {
        s.Advance()
    }
}

func complain( s *lexy.Source, message string ) {
    m := markers.Marker{span(s.Current), message}
    s.AddMarker( m )
}



